import { TGameObject } from './gameObjects/tGameObject';
import { Vector } from "./vector";

export const loadImg = (imgPath: string) => {
  return new Promise<HTMLImageElement>((resolve, reject) => {
    const img = new Image();
    img.src = imgPath;
    img.onload = () => {
      resolve(img);
    };
    img.onerror = (err) => {
      reject(err);
    };
  })
};

export const delay = async (time) => {
  return new Promise((resolve) => {
    setTimeout(resolve, time);
  });
};

export const matrixMultiply = (m1: number[][], m2: number[][]) => {
  const m1RowNum = m1.length;
  const m1ColumnNum = m1[0]?.length;

  const m2RowNum = m2.length;
  const m2ColumnNum = m2[0]?.length;

  if ((m1ColumnNum <= 0 || m2RowNum <= 0) || m1ColumnNum !== m2RowNum) {
    return null;
  }

  const res: number[][] = [];
  for (let rIndex = 0; rIndex < m1RowNum; ++rIndex) {
    const row = [];
    for (let cIndex = 0; cIndex < m2ColumnNum; ++cIndex) {
      row[cIndex] = m1[rIndex].reduce((acc, cur, index) => acc + cur * m2[index][cIndex], 0);
    }
    res[rIndex] = row;
  }

  return res;
};

export const createEmptyTranformMatrix = () => {
  return [ [1, 0, 0], [0, 1, 0], [0, 0, 1], ];
};

const measureCanvas = document.createElement("canvas");
const measureCtx = measureCanvas.getContext('2d');
export const measureText = (text: string, font: string, strokeStyle: string) => {
  if (!measureCtx) {
    throw new Error('measure text fail.');
  }

  measureCtx.save();
  if (font) { measureCtx.font = font; }
  if (strokeStyle) { measureCtx.strokeStyle = strokeStyle; }
  const textMetrics = measureCtx.measureText(text);
  const width = textMetrics.width;
  const height = textMetrics.actualBoundingBoxAscent + textMetrics.actualBoundingBoxDescent;
  console.log(width, height, textMetrics);
  measureCtx.restore();

  return { width, height };
};

const calcVertexs = (tGameObject: TGameObject) => {
  const rawVertexs = [{
    x: 0, y: 0,
  }, {
    x: tGameObject.width, y: 0,
  }, {
    x: tGameObject.width, y: tGameObject.height,
  }, {
    x: 0, y: tGameObject.height,
  },];

  const realVertexs = rawVertexs.map(el => {
    const {x: rawX, y: rawY} = el;
    const [[x], [y],] = matrixMultiply(tGameObject.transformMatrix, [[rawX], [rawY], [1]]);
    return {x, y};
  });
  return realVertexs;
};

const getProjection = (
  projectionVector: Vector,
  vertexs: {x: number, y: number}[]
) => {
    return vertexs.reduce((acc, vertex) => {
      const value = (new Vector(vertex.x, vertex.y)).dot(projectionVector);
      if (value < acc.min) { acc.min = value; }
      if (value > acc.max) { acc.max = value; }
      return acc;
    }, { max: Number.MIN_VALUE, min: Number.MAX_VALUE });
};

const getAllProjections = (vertexs: {x: number, y: number}[]) => {
  return vertexs.map((vertex, index) => {
    const nextVertex = (index >= vertexs.length - 1) ? vertexs[0] : vertexs[index + 1];
    return (new Vector(vertex.x, vertex.y))
      .subtract(new Vector(nextVertex.x, nextVertex.y))
      .prependicular()
      .normalize();
  });
};

const collisionDetectionHelper = (
  projectionVector: Vector,
  vertexs1: {x: number, y: number}[],
  vertexs2: {x: number, y: number}[]
) => {
    const { min: pMin1, max: pMax1, } = getProjection(projectionVector, vertexs1);
    const { min: pMin2, max: pMax2, } = getProjection(projectionVector, vertexs2);
    let min: number = Math.min(pMin1, pMin2);
    let max: number = Math.max(pMax1, pMax2);
    return (pMax1 - pMin1) + (pMax2 - pMin2) < (max - min);
};

export const isCollision = (
  gameObj1: TGameObject,
  gameObj2: TGameObject
) => {
  const vertexs1 = calcVertexs(gameObj1);
  const vertexs2 = calcVertexs(gameObj2);
  const allProjections = getAllProjections(vertexs1).concat(getAllProjections(vertexs2));
  for (const projectionVector of allProjections) {
    if (collisionDetectionHelper(projectionVector, vertexs1, vertexs2)) {
      return false;
    }
  }

  return true;
};
